package com.filldream.fastboot.core.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.filldream.fastboot.base.entity.OssResource;
import com.filldream.fastboot.base.mapper.OssResourceMapper;
import com.filldream.fastboot.common.constant.FinalStr;
import com.filldream.fastboot.common.entity.B;
import com.filldream.fastboot.common.entity.ErrorCode;
import com.filldream.fastboot.common.entity.R;
import com.filldream.fastboot.common.exception.GlobalException;
import com.filldream.fastboot.common.util.*;
import com.filldream.fastboot.core.event.OssEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

@Service
@Slf4j
public class UploadService {

    @Autowired
    private AliOssUtil aliOssUtil;

    @Autowired
    private OssResourceMapper ossResourceMapper;

    @Autowired
    private TxOcsUtil txOcsUtil;

    @Autowired
    private ApplicationContext applicationContext;

    @Value("${project.oss.imgServer}")
    private String urlPrefix;


    public List<OssResource> uploadOssFile(MultipartFile file, String filePrefix) {
        try {
            byte[] fileBytes = file.getBytes();
            OssResource result = initResult(file, fileBytes,filePrefix);

            //查看是否重复上传
            List<OssResource> hasList = getByMd5(result.getMd5Str(), 2);
            if(ObjectUtils.isNotEmpty(hasList)){
                return hasList;
            }

            String relativePath = uploadFileBytes(fileBytes, result.getFileName());
            applicationContext.publishEvent(new OssEvent( result.ok(relativePath)));
            return Arrays.asList(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public B delOssFile(String filePath) {
        return B.checkSuccess( aliOssUtil.delByFilePath(filePath) );
    }

    public List<OssResource> uploadCosFile(MultipartFile file, String filePrefix) throws IOException {
        OssResource result = initResult(file, file.getBytes(),filePrefix);
        List<OssResource> hasList = getByMd5(result.getMd5Str(), 3);
        if(ObjectUtils.isNotEmpty(hasList)){
            return hasList;
        }
        R<String> txResult = txOcsUtil.uploadFile(file, filePrefix);
        applicationContext.publishEvent(new OssEvent( result.ok(txResult.get("urlPrefix")+"")));
        return Arrays.asList(result);
    }


    public B delOcsFile(String filePath) {
        String key = filePath.substring(filePath.lastIndexOf("/"));
        return txOcsUtil.delFile(key);
    }

    public List<OssResource> uploadFileToLocal(MultipartFile file) {
        HttpServletRequest request = RequestContext.getRequest();
        String origin = getHostAndPort( request.getRequestURL().toString() + "/" + FinalStr.getContentPath());

        //原名称，例如:ws.jpg
        String originalFilename = file.getOriginalFilename();
        //原名称没有后缀，例如:ws
        String name = originalFilename.substring(0, originalFilename.lastIndexOf("."));

        OssResource result = null;
        try {
            result = initResult(file, file.getBytes(), name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        List<String> typeList = FinalStr.localFileNameFailChars;
        for (String item : typeList) {
            Assert.isBoolean( originalFilename.contains(item),ErrorCode.BASE_ERR.reMsg("文件名不合法，不能包含："+item) );
        }

        //校验文件类型
        Assert.contain(result.getFileSuffix().toLowerCase(), FinalStr.okFileSuffixs,"文件类型不合法：" + result.getFileSuffix());

        //新名称，例如：ws-xxxxx.jpg
        String newName = result.getFilePrefix() + "-" + RandomUtil.randomNumberString(10) + result.getFileSuffix();

        List<OssResource> hasList = getByMd5(result.getMd5Str(), 1);
        if(ObjectUtils.isNotEmpty(hasList)){
            return hasList;
        }

        //上传文件并校验是否上传成功
        Assert.isEmpty(FileIOUtil.saveFileToLocal(file, newName),ErrorCode.RESOURCE_UPLOAD_ERROR.reMsg());

        result.setFileName( newName );
        result.setUrlPrefix(origin + "/common/file/");

        applicationContext.publishEvent(new OssEvent( result.ok(newName)));

        return Arrays.asList( result );

    }


    //------------辅助方法--------------

    public String uploadFileBytes(byte[] file,String fileName) {
        String ossUrl = "";
        InputStream in = null;
        try {
            in = new ByteArrayInputStream(file);
            Assert.isEmpty(fileName, ErrorCode.BASE_ERR.reMsg("文件名不能为空"));
            ossUrl =aliOssUtil.uploadInputStream(in, fileName);
        }catch (Exception e){
            throw new GlobalException(ErrorCode.RESOURCE_UPLOAD_ERROR.reMsg());
        }finally {
            try {
                in.close();
            } catch (IOException e) {
                throw new GlobalException(ErrorCode.RESOURCE_UPLOAD_ERROR.reMsg(e.getMessage()));
            }
        }
        return ossUrl;
    }

    public OssResource initResult(MultipartFile file,byte[] bytes,String filePrefix){
        String fileSuffix = FileIOUtil.getFileSuffix(file.getOriginalFilename());
        //校验域名
        Assert.contain(fileSuffix.toLowerCase(),FinalStr.okFileSuffixs,"文件名不合法：" + fileSuffix);

        OssResource result = new OssResource();
        result.setFileSuffix( fileSuffix );
        result.setFileLength( bytes.length );
        result.setFileSize( FileIOUtil.getSize(bytes.length) );
        result.setOriginalName( file.getOriginalFilename() );
        result.setFilePrefix( FileIOUtil.genFileName(filePrefix)  );
        result.setFileName( result.getFilePrefix() + fileSuffix );
        result.setUrlPrefix( urlPrefix );
        result.setRelativePath( "" );
        result.setFileUrl( "" );
        result.setMd5Str(  FileIOUtil.toMd5(bytes) );
        return result;
    }

    public List<OssResource> getByMd5(String md5Str,Integer type){
        if( StringUtils.isNotBlank(md5Str) ){
            OssResource ossResource = ossResourceMapper.selectOne(new LambdaQueryWrapper<OssResource>()
                    .eq(OssResource::getMd5Str, md5Str)
                    .eq(OssResource::getType, type)
                    .last(" limit 1")
            );
            if( ossResource != null ){
                return Arrays.asList(ossResource);
            }
        }
        return Collections.emptyList();
    }

    /**
     * 下载或预览本地文件
     * @param response
     * @param fileName
     * @param isDownLoad
     */
    public void downloadLocalFile( HttpServletResponse response, String fileName,Boolean isDownLoad)  {
        isDownLoad = isDownLoad == null ? true : isDownLoad;
        try( FileInputStream fileInputStream = new FileInputStream(FinalStr.getLocalFileUrl() + File.separator + URLEncoder.encode(fileName)) ) {
            if(isDownLoad){
                response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode( FileIOUtil.getFileChineseName(fileName), "UTF-8"));
            }
            StreamUtils.copy(fileInputStream,response.getOutputStream());
        } catch (IOException e) {
            throw new GlobalException(ErrorCode.BASE_ERR.reMsg( (isDownLoad ? "下载" : "预览") + "失败"));
        }
    }


    //若压缩图片使用以下两个方法
    private static InputStream convertToJpg(byte[] file) throws IOException {
        BufferedImage read = ImageIO.read(new ByteArrayInputStream(file));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        BufferedImage jpgImg = new BufferedImage(read.getWidth(), read.getHeight(), BufferedImage.TYPE_INT_RGB);
        jpgImg.createGraphics().drawImage(read, 0, 0, Color.WHITE, null);
        ImageIO.write(jpgImg, "jpg", out);
        return new ByteArrayInputStream(out.toByteArray());
    }
    private static InputStream zipImg(InputStream stream) {
        try {
            BufferedImage read = ImageIO.read(stream);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            // 得到指定Format图片的writer
            Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");// 得到迭代器
            ImageWriter writer = (ImageWriter) iter.next(); // 得到writer

            // 得到指定writer的输出参数设置(ImageWriteParam )
            ImageWriteParam iwp = writer.getDefaultWriteParam();
            iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
            iwp.setCompressionQuality(0.3f); // 设置压缩质量参数

            iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
            ColorModel colorModel = ColorModel.getRGBdefault();
            // 指定压缩时使用的色彩模式
            iwp.setDestinationType(
                    new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16)));

            // 开始打包图片，写入byte[]
            IIOImage iIamge = new IIOImage(read, null, null);
            // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
            // 通过ImageIo中的静态方法，得到byteArrayOutputStream的ImageOutput
            writer.setOutput(ImageIO.createImageOutputStream(out));
            writer.write(null, iIamge, iwp);
            return new ByteArrayInputStream(out.toByteArray());
        } catch (IOException e) {
            throw new GlobalException(ErrorCode.IMG_ZIP_ERROR.reMsg());
        }
    }

    /**
     * 获取请求地址的域名和端口
     * 例如：
     * http://localhost:8090/common/upload/uploadFileToLocal
     * http://pc.baidu.com/api-dt3feature/common/store/storeList
     * 返回：
     * http://localhost:8090
     * http://pc.baidu.com
     * @param requestUrl
     * @return
     */
    public static String getHostAndPort(String requestUrl){
        URL url = null;
        try {
            url = new URL(requestUrl);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
        // 获取主机部分
        String host = url.getHost();
        // 获取端口部分，如果未明确指定端口，则返回 -1
        int port = url.getPort();
        // 获取协议部分
        String protocol = url.getProtocol();
        return protocol + "://" + host + ( port > 0 ? (":" + port) : "" );
    }

}
